/*
 * BackgroundWorker.java
 *
 * Copyright 2007 Tiberiumlabs
 *
 * This file is part of Tiberiumlabs Shards.
 *
 * Tiberiumlabs Shards is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tiberiumlabs Shards is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.tiberiumlabs.shards.toolkit;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author <a href="mailto:paranoid.tiberiumlabs@gmail.com">Paranoid</a>
 */
public abstract class BackgroundWorker<E> {

    // <editor-fold defaultstate="collapsed" desc=" Public Intergace part ">
    
    public BackgroundWorker() {
        worker = new Thread(new Worker());
        worker.setPriority(priority);
        worker.setDaemon(true);
        worker.start();
    }

    /**
     * Override this method to perform some task with each element you submitted.<br/>
     * Parameter is submitter here in order they were added to method.<br/>
     * If exceptions will occur - it will not break all work, but just one element will be lost.<br/>
     */
    public abstract void doInBackground(E e);

    private int priority = Thread.NORM_PRIORITY-1;

    /**
     * @return this worker priority
     */
    public final int getPriority() {
        return priority;
    }

    /**
     * Changes priority of this worker.<br/>
     * Default priority is {@code Thread.NORM_PRIORITY}.
     * @param priority priority to set for this worker
     */
    public final void setPriority(int priority) {
        this.priority = priority;
    }

    private final LinkedList<E> tasksList = new LinkedList<E>();

    /**
     * Executes {@code doInBackground} method on this element in the future.<br/>
     * Excecution will be in same order as elements were added.<br/>
     * If worker thread is not already working - it will be created and started.<br/>
     * If worker is already created - this task will pass to {@code doInBackground}
     * after all current tasks will be completed.<br/>
     * Once task is submitted to worker - it can be removed only by {@code purge} method.<br/>
     */
    public final void add(E e) {
        synchronized (tasksList) {
            tasksList.addLast(e);
        }
        notifyWorker();
    }

    /**
     * Executes {@code doInBackground} method on this elements in the future.<br/>
     * Excecution will be in same order as elements were added.<br/>
     * If worker thread is not already working - it will be created and started.<br/>
     * If worker is already created - this tasks will pass to {@code doInBackground}
     * after all current tasks will be completed.<br/>
     */
    public final void addAll(Collection<? extends E> collection) {
        synchronized (tasksList) {
            tasksList.addAll(collection);
        }
        notifyWorker();
    }
    
    /**
     * Clears current tasks list, so worker will stop after current iteration is done.
     */
    public final void purge() {
        synchronized (tasksList) {
            tasksList.clear();
        }
    }
    
    /**
     * Returns current tasks list. This list is unmodifiable, so you
     * can't add or remove tasks from it.
     * @return current tasks list.
     */
    public final List<E> getTasksList() {
        return Collections.unmodifiableList(tasksList);
    }
    
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc=" internal part ">
    private Thread worker;

    private final void notifyWorker() {
        if (!tasksList.isEmpty()) {
            synchronized (this) {
                notify();
            }
        }
    }
    
    private final void process() {
        while (true) {
            if (tasksList.isEmpty()) {
                try {
                    synchronized (this) {
                        wait();
                    }
                } catch (InterruptedException ignore) {
                }
            }
            while (tasksList.size() > 0) {
                E e;
                synchronized (tasksList) {
                    e = tasksList.removeFirst();
                }
                doInBackground(e);
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.err.println("WARNING: There are some uncompleted tasks in " + this.getClass().getName() + ':');
        while (tasksList.size() > 0) {
            System.err.println(tasksList.removeFirst());
        }
        super.finalize();
    }
    // </editor-fold>

    private class Worker implements Runnable {
        @Override
        public void run() {
            process();
        }
    }

}
